﻿/*
--------------------------------------
File: KMeansAlgorithm.cs
Project: Themaopdracht 7
Author: Lars Hartmann

Description:
KMeans clustering algorithm that can return a monochrome if wanted.
--------------------------------------
*/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace THO7_Team8
{
	internal class KMeansAlgorithm : VisionAlgorithm
	{
		private int origWidth, origHeight, origStride, bpp;
		private byte[] rgbValues;
		private Boolean monochromeResult;

		public KMeansAlgorithm(String name)
			: base(name)
		{
			hasConfiguration = true;
			configuration = new KMeansConfiguration();
		}

		public override System.Drawing.Bitmap DoAlgorithm(System.Drawing.Bitmap sourceImage)
		{
			String configvalueAsString = "" + _configValue;
			int nrOfClusters = Convert.ToInt16(configvalueAsString.Substring(1)), nrOfIterations = 10;
			monochromeResult = configvalueAsString.Substring(0, 1).Equals("1");

			origWidth = sourceImage.Width;
			origHeight = sourceImage.Height;

			Bitmap newBitmap = new Bitmap(origWidth, origHeight, sourceImage.PixelFormat);

			BitmapData bmpData = sourceImage.LockBits(new Rectangle(0, 0, origWidth, origHeight), System.Drawing.Imaging.ImageLockMode.ReadWrite, sourceImage.PixelFormat);

			origStride = bmpData.Stride;
			bpp = origStride / origWidth;
			IntPtr ptr = bmpData.Scan0;
			int bytes = Math.Abs(origStride) * origHeight;
			rgbValues = new byte[bytes];

			Marshal.Copy(ptr, rgbValues, 0, bytes);
			sourceImage.UnlockBits(bmpData);

			KMeans_Cluster[] clusters = generateRandomClusters(nrOfClusters);
			int [,] pixelGroup = new int[0, 0];
			for(int t = 0; t < nrOfIterations; t++)
			{
				pixelGroup = groupPixels(clusters);
				shiftClusters(clusters, pixelGroup);
			}

			if(monochromeResult)
				monochromeRgbValues(pixelGroup);
			else
				changeColors(clusters, pixelGroup);

			bmpData = newBitmap.LockBits(new Rectangle(0, 0, origWidth, origHeight), ImageLockMode.ReadWrite, newBitmap.PixelFormat);

			ptr = bmpData.Scan0;
			Marshal.Copy(rgbValues, 0, ptr, bytes);
			newBitmap.UnlockBits(bmpData);

			return newBitmap;
		}

		private void monochromeRgbValues(int[,] pixelGroup)
		{
			for(int y = 0; y < origHeight; y++)
			{
				for(int x = 0; x < origWidth; x++)
				{
					if(pixelGroup[x, y] == 0)
					{
						rgbValues[(x * bpp + origStride * y)] = (byte) 255;
						rgbValues[(x * bpp + origStride * y) + 1] = (byte) 255;
						rgbValues[(x * bpp + origStride * y) + 2] = (byte) 255;
					}
					else
					{
						rgbValues[(x * bpp + origStride * y)] = (byte) 0;
						rgbValues[(x * bpp + origStride * y) + 1] = (byte) 0;
						rgbValues[(x * bpp + origStride * y) + 2] = (byte) 0;
					}
				}
			}
		}

		private void shiftClusters(KMeans_Cluster[] clusters, int[,] pixelGroup)
		{
			foreach(KMeans_Cluster c in clusters)
			{
				int t = 0, r = 0, g = 0, b = 0, nr = c.getNr();
				for(int y = 0; y < origHeight; y++)
				{
					for(int x = 0; x < origWidth; x++)
					{
						if(pixelGroup[x, y] == nr)
						{
							t++;
							b += rgbValues[(x * bpp + origStride * y)];
							g += rgbValues[(x * bpp + origStride * y) + 1];
							r += rgbValues[(x * bpp + origStride * y) + 2];
						}
					}
				}

				if(t > 0)
				{
					r /= t;
					g /= t;
					b /= t;

					c.setColor(r, g, b);
				}
			}
		}

		private void changeColors(KMeans_Cluster[] clusters, int[,] pixelGroup)
		{
			for(int y = 0; y < origHeight; y++)
			{
				for(int x = 0; x < origWidth; x++)
				{
					KMeans_Cluster c = clusters[pixelGroup[x, y]];
					rgbValues[(x * bpp + origStride * y)] = (byte) c.getB();
					rgbValues[(x * bpp + origStride * y) + 1] = (byte) c.getG();
					rgbValues[(x * bpp + origStride * y) + 2] = (byte) c.getR();
				}
			}
		}

		private int[,] groupPixels(KMeans_Cluster[] clusters)
		{
			foreach(KMeans_Cluster c in clusters)
			{
				c.sizeReset();
			}

			int[,] pixelGroup = new int[origWidth, origHeight];
			for(int y = 0; y < origHeight; y++)
			{
				for(int x = 0; x < origWidth; x++)
				{
					int b = rgbValues[(x * bpp + origStride * y)],
					g = rgbValues[(x * bpp + origStride * y) + 1],
					r = rgbValues[(x * bpp + origStride * y) + 2];

					double distance = Double.MaxValue;
					foreach(KMeans_Cluster c in clusters)
					{
						int rD, gD, bD;
						double tempD;
						rD = r - c.getR();
						gD = g - c.getG();
						bD = b - c.getB();
						tempD = Math.Sqrt(bD * bD + gD * gD + rD * rD);
						if(tempD < distance)
						{
							distance = tempD;
							pixelGroup[x, y] = c.getNr();
						}
					}
					clusters[pixelGroup[x, y]].sizeIncr();
				}
			}

			return pixelGroup;
		}

		private KMeans_Cluster[] generateRandomClusters(int nrOfClusters)
		{
			KMeans_Cluster[] clusterArray = new KMeans_Cluster[nrOfClusters];
			clusterArray[0] = new KMeans_Cluster(229, 199, 7, 0);

			Random rng = new Random();
			for(int i = 1; i < nrOfClusters; i++)
			{
				int r, g, b, randomX, randomY;

				randomX = rng.Next(origWidth);
				randomY = rng.Next(origHeight);

				b = rgbValues[(randomX * bpp + origStride * randomY)];
				g = rgbValues[(randomX * bpp + origStride * randomY) + 1];
				r = rgbValues[(randomX * bpp + origStride * randomY) + 2];

				Boolean exists = false;
				int range = 50;
				foreach(KMeans_Cluster c in clusterArray)
				{
					if(c != null)
					{
						if((b + range >= c.getB() && b - range <= c.getB()) &&
							(g + range >= c.getG() && g - range <= c.getG()) &&
							(r + range >= c.getR() && r - range <= c.getR()))
						{
							exists = true;
							break;
						}
					}
				}

				if(!exists)
					clusterArray[i] = new KMeans_Cluster(r, g, b, i);
				else
					i--;
			}

			return clusterArray;
		}
	}
}